package parser;

import antlr.RecognitionException;
import antlr.Token;
import java.util.*;
import java.math.*;
import java.io.PrintStream;

class ConstantTable {

    BigInteger MAX_BYTE = new BigInteger("127");
    BigInteger MIN_BYTE = new BigInteger("-128");
    BigInteger MAX_INT  = new BigInteger("32767");
    BigInteger MIN_INT  = new BigInteger("-32768");
        
    Map<Object, Integer> table = new HashMap<Object, Integer>();
    
    Integer constant(Object c) {
        Integer i = table.get(c);
        if (i == null) {
            i = table.size();
            table.put(c, i);
        }
        return i;    
    }

    Expr getString(String s) {
        return new StringConstant(constant(s), s);
    }

    Expr getInt(Token t) {
        BigInteger b;
        String text = t.getText();
        char c = text.charAt(0);
        if (c == '0' && text.length() > 1) {
            c = text.charAt(1);
            if (c == 'x' || c == 'X') {
                b = new BigInteger(text.substring(2), 16);
            } else {
                b = new BigInteger(text, 8);
            }
        } else {
            b = new BigInteger(text);
        }
        if(b.compareTo(MAX_BYTE) <= 0 && b.compareTo(MIN_BYTE) >= 0) {
            return new Byte(b.intValue());
        } else {
            return new Constant(constant(b));
        }
    }
    
    Expr getLong(Token t) {
        String text = t.getText();
        char l = text.charAt(text.length() -1);
        BigInteger b;
        if (l == 'l' || l == 'L') {
            b = new BigInteger(text.substring(0, text.length() -1));
        } else {
            b = new BigInteger(text);
        }
        return new Constant(constant(b));
    }
    
    Expr getFloat(Token t) {
        Double d = Double.valueOf(t.getText());
        return new Constant(constant(d));
    }
    
    Expr getComplex(Token t) {
        String text = t.getText();
        char j = text.charAt(text.length() -1);
        Double d;
        if (j == 'j' || j == 'J') {
            d = Double.valueOf(text.substring(0, text.length() -1));
        } else {
            d = Double.valueOf(text);
        }
        return new Constant(constant(d));
    }
    
    Map flipMap(Map m) {
        Map result = new HashMap();
        for (Object o : m.entrySet()) {
            Map.Entry e = (Map.Entry)o;
            result.put(e.getValue(), e.getKey());   
        }
        return result;
    }
    
    void writeString(ByteStream out, String name) {
        out.write(3); /* String Type */
        int start = out.here();
        out.writeShort(0);
        int len = name.length();
        for (int i = 0; i < len; i++) {
            out.writeUTF8(name.charAt(i));
        }
        out.patchShort(start, out.here()-start -2);
     }

    void write(ByteStream out) {
        Map consts = flipMap(table);
        int size = consts.size();
        out.write(size >> 8);
        out.write(size & 0xff);
        for (int i = 0; i < size; i++) {
            Object o = consts.get(i);
            if (o instanceof String) {
                writeString(out, (String)o);
            } else if (o instanceof BigInteger) {
                byte[] bytes = ((BigInteger)o).toByteArray();
                int len = bytes.length;
                out.write(0); /* Long type */
                out.writeShort(len);
                for (int j = 0; j < len; j++) {
                    out.write(bytes[j]);
                }
            } else if (o instanceof Double) {
                out.write(1); /* Float type */
                out.writeShort(8);
                long l = Double.doubleToRawLongBits((Double)o);
                out.write((int)(l >> 56));
                out.write((int)(l >> 48));
                out.write((int)(l >> 40));
                out.write((int)(l >> 32));
                out.write((int)(l >> 24));
                out.write((int)(l >> 16));
                out.write((int)(l >> 8));
                out.write((int)l);
            } else {
                throw new Error("Unexpected class " + o.getClass());
            }
        }
    }

    void dump(PrintStream out) {
        Map consts = flipMap(table);
        int size = consts.size();
        out.println("Constants Table size: " + size);
        for (int i = 0; i < size; i++) {
            out.println(consts.get(i));
        }
    }

}

class Byte extends Expr {
    
    int value;
    
    Byte(int value) {
        this.value = value;
    }   
    
    Expr negate() {
        if (value == -128)
            return super.negate();
        else
            return new Byte(-value);
    }
    
    void write(ByteStream out) {
        out.write(Opcodes.BYTE);
        out.write(value);
    }

    void delete(ByteStream out) throws RecognitionException {
        throw new RecognitionException("Cannot delete constant");
    }
    
     
    void writeLHS(ByteStream out) throws RecognitionException {
        throw new RecognitionException("Cannot assign to a constant");
    }
      
}

class Constant extends Expr {
    
    int value;
    
    Constant(int value) {
        this.value = value;
    }   
    
    void write(ByteStream out) {
        out.write(Opcodes.CONSTANT);
        out.writeShort(value);
    }

    void delete(ByteStream out) throws RecognitionException {
        throw new RecognitionException("Cannot delete constant");
    }
     
    void writeLHS(ByteStream out) throws RecognitionException {
        throw new RecognitionException("Cannot assign to a constant");
    }
   
    public Stmt toStm() {
        return new Stmt() {
            void write(ByteStream out) throws RecognitionException {
                out.line(line);
            }
        };
    }
}

class StringConstant extends Constant {
    
    String doc;
    
    StringConstant(int value, String s) {
        super(value);
        doc = s;
    }   

    public Stmt toStm() {
        return new DocString(doc);
    }
    
}
